package manager

import (
	"errors"
	"happy-sudoku/internal/model"
	"happy-sudoku/internal/repository"
	"happy-sudoku/internal/sudoku"
	"happy-sudoku/internal/util"
)

// IPuzzleManager ...
type IPuzzleManager interface {
	GetOne(puzzleID int) (*model.Puzzle, error)
	Check(puzzleID int, ansGrid sudoku.GridType) (*sudoku.CheckResType, error)
	GetPuzzleByID(puzzleID int) (*model.Puzzle, error)
	GetPuzzleByDifficulty(difficulty model.Difficulty) (*model.Puzzle, error)
}

// PuzzleManager implements IPuzzleManager
type PuzzleManager struct {
	repo     repository.IPuzzleRepo
	checkers util.SyncIntMapSudokuAnswerCheckService
}

// NewPuzzleManager new puzzle manager
func NewPuzzleManager(repo repository.IPuzzleRepo) *PuzzleManager {
	return &PuzzleManager{
		repo:     repo,
		checkers: util.MakeSyncIntMapSudokuAnswerCheckService(),
	}
}

// GetOne get one puzzle
func (p *PuzzleManager) GetOne(puzzleID int) (*model.Puzzle, error) {
	return p.repo.GetOne(puzzleID)
}

// Check checks puzzle correctness
func (p *PuzzleManager) Check(puzzleID int, ansGrid sudoku.GridType) (*sudoku.CheckResType, error) {
	// if puzzle not exist
	puzzle, err := p.repo.GetOne(puzzleID)
	if err != nil {
		return nil, err
	}
	var checker *sudoku.AnswerCheckService
	checker, _ = p.checkers.StoreOrLoadWait(puzzleID, func() (*sudoku.AnswerCheckService, error) {
		var grid sudoku.GridType
		// also save err
		grid, err = sudoku.MakeGridFromString(puzzle.Content)
		if err != nil {
			return nil, err
		}
		// set new checker
		checker = sudoku.NewAnswerCheckService(grid)
		return checker, nil
	})

	// if stored with error
	if err != nil {
		return nil, err
	}

	// if loaded with error
	if checker == nil {
		return nil, errors.New("Loaded error")
	}

	res := checker.Check(ansGrid)
	return &res, nil
}

// GetPuzzleByID get puzzles
func (p *PuzzleManager) GetPuzzleByID(puzzleID int) (*model.Puzzle, error) {
	return p.repo.GetOne(puzzleID)
}

// GetPuzzleByDifficulty get puzzle by difficulty
func (p *PuzzleManager) GetPuzzleByDifficulty(difficulty model.Difficulty) (*model.Puzzle, error) {
	// TODO: Implement Random Choose
	return p.repo.GetOneByQuery("difficulty = ?", difficulty.String())
}
